<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ROS2小车运动控制</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#165DFF',
                        secondary: '#36CFC9',
                        warning: '#FF7D00',
                        danger: '#F53F3F',
                        success: '#00B42A',
                        dark: '#1D2129',
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .control-btn {
                @apply w-16 h-16 md:w-20 md:h-20 flex items-center justify-center rounded-lg text-white text-xl md:text-2xl cursor-pointer transition-all duration-200 shadow-lg;
            }
            .control-btn:active {
                @apply transform scale-95;
            }
            .joystick-area {
                @apply w-full max-w-md aspect-square mx-auto bg-gray-100 rounded-full relative overflow-hidden;
            }
            .joystick-knob {
                @apply w-24 h-24 bg-primary rounded-full absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 shadow-lg cursor-grab active:cursor-grabbing transition-all duration-100;
            }
        }
    </style>
</head>
<body class="bg-gray-50 min-h-screen flex flex-col">
    <header class="bg-white shadow-md py-4 px-6">
        <div class="container mx-auto flex justify-between items-center">
            <h1 class="text-[clamp(1.5rem,3vw,2rem)] font-bold text-dark flex items-center">
                <i class="fa fa-car text-primary mr-3"></i>ROS2小车运动控制
            </h1>
            <div class="flex items-center space-x-4">
                <div class="flex items-center">
                    <span id="connection-status-indicator" class="w-3 h-3 rounded-full bg-gray-300 mr-2"></span>
                    <span id="connection-status-text" class="text-gray-600">未连接</span>
                </div>
                <button id="connect-btn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition-colors">
                    <i class="fa fa-plug mr-1"></i>连接小车
                </button>
            </div>
        </div>
    </header>

    <main class="flex-grow container mx-auto p-4 md:p-6">
        <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
            <!-- 控制区域 -->
            <div class="lg:col-span-2 space-y-6">
                <!-- 方向键控制 -->
                <div class="bg-white rounded-xl shadow-md p-6">
                    <h2 class="text-lg font-semibold text-gray-800 mb-6 flex items-center">
                        <i class="fa fa-arrows text-primary mr-2"></i>方向键控制
                    </h2>
                    <div class="max-w-md mx-auto">
                        <div class="grid grid-cols-3 gap-3">
                            <div></div>
                            <button id="btn-forward" class="control-btn bg-primary hover:bg-primary/90">
                                <i class="fa fa-arrow-up"></i>
                            </button>
                            <div></div>
                            <button id="btn-left" class="control-btn bg-primary hover:bg-primary/90">
                                <i class="fa fa-arrow-left"></i>
                            </button>
                            <button id="btn-stop" class="control-btn bg-danger hover:bg-danger/90">
                                <i class="fa fa-stop"></i>
                            </button>
                            <button id="btn-right" class="control-btn bg-primary hover:bg-primary/90">
                                <i class="fa fa-arrow-right"></i>
                            </button>
                            <div></div>
                            <button id="btn-backward" class="control-btn bg-primary hover:bg-primary/90">
                                <i class="fa fa-arrow-down"></i>
                            </button>
                            <div></div>
                        </div>
                    </div>
                </div>

                <!-- 虚拟摇杆控制 -->
                <div class="bg-white rounded-xl shadow-md p-6">
                    <h2 class="text-lg font-semibold text-gray-800 mb-6 flex items-center">
                        <i class="fa fa-gamepad text-primary mr-2"></i>虚拟摇杆控制
                    </h2>
                    <div class="joystick-area" id="joystick">
                        <div class="joystick-knob" id="joystick-knob"></div>
                    </div>
                </div>
            </div>

            <!-- 参数控制 -->
            <div class="space-y-6">
                <!-- 速度控制 -->
                <div class="bg-white rounded-xl shadow-md p-6">
                    <h2 class="text-lg font-semibold text-gray-800 mb-6 flex items-center">
                        <i class="fa fa-tachometer text-primary mr-2"></i>速度控制
                    </h2>
                    <div class="space-y-6">
                        <div>
                            <label class="block text-sm font-medium text-gray-700 mb-2">
                                线性速度: <span id="linear-speed-value" class="text-primary font-semibold">0.5</span> m/s
                            </label>
                            <input type="range" id="linear-speed" min="0" max="1" step="0.1" value="0.5"
                                class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary">
                        </div>
                        <div>
                            <label class="block text-sm font-medium text-gray-700 mb-2">
                                角速度: <span id="angular-speed-value" class="text-primary font-semibold">0.8</span> rad/s
                            </label>
                            <input type="range" id="angular-speed" min="0" max="2" step="0.1" value="0.8"
                                class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer accent-primary">
                        </div>
                    </div>
                </div>

                <!-- 状态信息 -->
                <div class="bg-white rounded-xl shadow-md p-6">
                    <h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
                        <i class="fa fa-info-circle text-primary mr-2"></i>当前状态
                    </h2>
                    <div class="space-y-3 text-sm">
                        <div class="flex justify-between py-2 border-b border-gray-100">
                            <span class="text-gray-600">小车IP:</span>
                            <span id="robot-ip" class="font-medium">192.168.56.102</span>
                        </div>
                        <div class="flex justify-between py-2 border-b border-gray-100">
                            <span class="text-gray-600">WebSocket状态:</span>
                            <span id="websocket-status" class="font-medium text-gray-500">未连接</span>
                        </div>
                        <div class="flex justify-between py-2 border-b border-gray-100">
                            <span class="text-gray-600">当前指令:</span>
                            <span id="current-command" class="font-medium text-gray-500">停止</span>
                        </div>
                        <div class="flex justify-between py-2 border-b border-gray-100">
                            <span class="text-gray-600">发送频率:</span>
                            <span id="send-frequency" class="font-medium">10 Hz</span>
                        </div>
                    </div>
                </div>

                <!-- 操作说明 -->
                <div class="bg-white rounded-xl shadow-md p-6">
                    <h2 class="text-lg font-semibold text-gray-800 mb-4 flex items-center">
                        <i class="fa fa-lightbulb-o text-warning mr-2"></i>操作说明
                    </h2>
                    <ul class="text-sm text-gray-600 space-y-2">
                        <li class="flex items-start">
                            <i class="fa fa-check-circle text-success mt-1 mr-2"></i>
                            <span>先点击"连接小车"按钮建立通信</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-check-circle text-success mt-1 mr-2"></i>
                            <span>使用方向键或虚拟摇杆控制小车移动</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-check-circle text-success mt-1 mr-2"></i>
                            <span>调整滑块可设置最大速度</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fa fa-check-circle text-success mt-1 mr-2"></i>
                            <span>松开所有按键小车将停止</span>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
    </main>

    <footer class="bg-white border-t border-gray-200 py-4 px-6 mt-8">
        <div class="container mx-auto text-center text-sm text-gray-500">
            <p>ROS2小车运动控制界面 &copy; 2023</p>
        </div>
    </footer>

    <script>
        // DOM元素
        const connectBtn = document.getElementById('connect-btn');
        const connectionStatusIndicator = document.getElementById('connection-status-indicator');
        const connectionStatusText = document.getElementById('connection-status-text');
        const websocketStatus = document.getElementById('websocket-status');
        const currentCommand = document.getElementById('current-command');
        const linearSpeedSlider = document.getElementById('linear-speed');
        const linearSpeedValue = document.getElementById('linear-speed-value');
        const angularSpeedSlider = document.getElementById('angular-speed');
        const angularSpeedValue = document.getElementById('angular-speed-value');
        const btnForward = document.getElementById('btn-forward');
        const btnBackward = document.getElementById('btn-backward');
        const btnLeft = document.getElementById('btn-left');
        const btnRight = document.getElementById('btn-right');
        const btnStop = document.getElementById('btn-stop');
        const joystick = document.getElementById('joystick');
        const joystickKnob = document.getElementById('joystick-knob');

        // ROS2小车配置
        const ROBOT_IP = '192.168.56.102';
        const ROSBRIDGE_PORT = 9090;
        const CMD_VEL_TOPIC = '/cmd_vel';
        const UPDATE_FREQUENCY = 10; // 控制指令发送频率(Hz)

        // 全局变量
        let websocket = null;
        let isConnected = false;
        let linearSpeed = 0.5; // 默认线速度
        let angularSpeed = 0.8; // 默认角速度
        let currentLinearX = 0;
        let currentAngularZ = 0;
        let joystickActive = false;
        let sendInterval = null;

        // 更新速度显示
        linearSpeedSlider.addEventListener('input', () => {
            linearSpeed = parseFloat(linearSpeedSlider.value);
            linearSpeedValue.textContent = linearSpeed.toFixed(1);
        });

        angularSpeedSlider.addEventListener('input', () => {
            angularSpeed = parseFloat(angularSpeedSlider.value);
            angularSpeedValue.textContent = angularSpeed.toFixed(1);
        });

        // 连接ROS2小车
        connectBtn.addEventListener('click', () => {
            if (isConnected) {
                disconnectFromRobot();
            } else {
                connectToRobot();
            }
        });

        // 连接到ROS2小车
        function connectToRobot() {
            const websocketUrl = `ws://${ROBOT_IP}:${ROSBRIDGE_PORT}`;
            websocket = new WebSocket(websocketUrl);

            websocket.onopen = () => {
                console.log('WebSocket连接已建立');
                isConnected = true;
                updateConnectionStatus(true);
                startSendingCommands();
            };

            websocket.onclose = () => {
                console.log('WebSocket连接已关闭');
                isConnected = false;
                updateConnectionStatus(false);
                stopSendingCommands();
                // 自动重连
                setTimeout(() => connectToRobot(), 3000);
            };

            websocket.onerror = (error) => {
                console.error('WebSocket错误:', error);
                isConnected = false;
                updateConnectionStatus(false);
            };

            websocket.onmessage = (event) => {
                // 可以处理来自ROS2的消息
                console.log('收到消息:', event.data);
            };
        }

        // 断开与ROS2小车的连接
        function disconnectFromRobot() {
            if (websocket) {
                websocket.close();
                websocket = null;
            }
            isConnected = false;
            updateConnectionStatus(false);
            stopSendingCommands();
        }

        // 更新连接状态显示
        function updateConnectionStatus(connected) {
            if (connected) {
                connectionStatusIndicator.className = 'w-3 h-3 rounded-full bg-success mr-2';
                connectionStatusText.textContent = '已连接';
                websocketStatus.textContent = '已连接';
                websocketStatus.className = 'font-medium text-success';
                connectBtn.innerHTML = '<i class="fa fa-unplug mr-1"></i>断开连接';
                connectBtn.className = 'bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors';
            } else {
                connectionStatusIndicator.className = 'w-3 h-3 rounded-full bg-danger mr-2';
                connectionStatusText.textContent = '未连接';
                websocketStatus.textContent = '未连接';
                websocketStatus.className = 'font-medium text-danger';
                connectBtn.innerHTML = '<i class="fa fa-plug mr-1"></i>连接小车';
                connectBtn.className = 'bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-lg transition-colors';
            }
        }

        // 开始发送控制指令
        function startSendingCommands() {
            // 停止之前的定时器
            if (sendInterval) {
                clearInterval(sendInterval);
            }

            // 设置新的定时器
            const intervalMs = 1000 / UPDATE_FREQUENCY;
            sendInterval = setInterval(sendVelocityCommand, intervalMs);
        }

        // 停止发送控制指令
        function stopSendingCommands() {
            if (sendInterval) {
                clearInterval(sendInterval);
                sendInterval = null;
            }
            // 发送停止指令
            sendStopCommand();
        }

        // 发送速度控制指令
        function sendVelocityCommand() {
            if (!isConnected || !websocket) return;

            // 创建ROS2 Twist消息
            const twistMessage = {
                op: 'publish',
                topic: CMD_VEL_TOPIC,
                msg: {
                    linear: {
                        x: currentLinearX,
                        y: 0,
                        z: 0
                    },
                    angular: {
                        x: 0,
                        y: 0,
                        z: currentAngularZ
                    }
                }
            };

            // 发送消息
            try {
                websocket.send(JSON.stringify(twistMessage));
                updateCommandDisplay();
            } catch (error) {
                console.error('发送消息失败:', error);
            }
        }

        // 发送停止指令
        function sendStopCommand() {
            currentLinearX = 0;
            currentAngularZ = 0;
            sendVelocityCommand();
        }

        // 更新指令显示
        function updateCommandDisplay() {
            if (currentLinearX === 0 && currentAngularZ === 0) {
                currentCommand.textContent = '停止';
                currentCommand.className = 'font-medium text-gray-500';
            } else {
                let commandText = '';
                if (currentLinearX > 0) commandText += '前进 ';
                if (currentLinearX < 0) commandText += '后退 ';
                if (currentAngularZ > 0) commandText += '左转 ';
                if (currentAngularZ < 0) commandText += '右转 ';
                currentCommand.textContent = commandText.trim();
                currentCommand.className = 'font-medium text-primary';
            }
        }

        // 方向键控制
        btnForward.addEventListener('mousedown', () => currentLinearX = linearSpeed);
        btnForward.addEventListener('touchstart', (e) => {
            e.preventDefault();
            currentLinearX = linearSpeed;
        });

        btnBackward.addEventListener('mousedown', () => currentLinearX = -linearSpeed);
        btnBackward.addEventListener('touchstart', (e) => {
            e.preventDefault();
            currentLinearX = -linearSpeed;
        });

        btnLeft.addEventListener('mousedown', () => currentAngularZ = angularSpeed);
        btnLeft.addEventListener('touchstart', (e) => {
            e.preventDefault();
            currentAngularZ = angularSpeed;
        });

        btnRight.addEventListener('mousedown', () => currentAngularZ = -angularSpeed);
        btnRight.addEventListener('touchstart', (e) => {
            e.preventDefault();
            currentAngularZ = -angularSpeed;
        });

        btnStop.addEventListener('mousedown', sendStopCommand);
        btnStop.addEventListener('touchstart', (e) => {
            e.preventDefault();
            sendStopCommand();
        });

        // 释放按键时停止
        document.addEventListener('mouseup', () => {
            currentLinearX = 0;
            currentAngularZ = 0;
        });

        document.addEventListener('touchend', () => {
            currentLinearX = 0;
            currentAngularZ = 0;
        });

        // 虚拟摇杆控制
        joystick.addEventListener('mousedown', startJoystick);
        joystick.addEventListener('touchstart', (e) => {
            e.preventDefault();
            startJoystick(e.touches[0]);
        });

        document.addEventListener('mousemove', moveJoystick);
        document.addEventListener('touchmove', (e) => {
            e.preventDefault();
            moveJoystick(e.touches[0]);
        });

        document.addEventListener('mouseup', stopJoystick);
        document.addEventListener('touchend', stopJoystick);

        function startJoystick(event) {
            joystickActive = true;
            joystickKnob.classList.add('scale-110');
            moveJoystick(event);
        }

        function moveJoystick(event) {
            if (!joystickActive) return;

            const rect = joystick.getBoundingClientRect();
            const centerX = rect.width / 2;
            const centerY = rect.height / 2;
            const maxDistance = centerX * 0.8; // 限制在80%范围内

            // 获取触摸/鼠标位置
            const x = event.clientX - rect.left - centerX;
            const y = event.clientY - rect.top - centerY;

            // 计算距离中心的距离
            const distance = Math.sqrt(x * x + y * y);

            // 如果超出最大距离，按比例缩小
            let limitedX = x;
            let limitedY = y;
            if (distance > maxDistance) {
                limitedX = (x / distance) * maxDistance;
                limitedY = (y / distance) * maxDistance;
            }

            // 更新摇杆位置
            joystickKnob.style.transform = `translate(${limitedX}px, ${limitedY}px)`;

            // 计算速度值 (-1 到 1)
            const normalizedX = limitedX / maxDistance;
            const normalizedY = limitedY / maxDistance;

            // 设置速度 (Y轴反转，因为上方向是负值)
            currentLinearX = -normalizedY * linearSpeed;
            currentAngularZ = normalizedX * angularSpeed;
        }

        function stopJoystick() {
            if (joystickActive) {
                joystickActive = false;
                joystickKnob.classList.remove('scale-110');
                joystickKnob.style.transform = 'translate(-50%, -50%)';
                currentLinearX = 0;
                currentAngularZ = 0;
            }
        }

        // 页面关闭时发送停止指令
        window.addEventListener('beforeunload', () => {
            sendStopCommand();
        });
    </script>
</body>
</html>